package org.farng.mp3.id3;

import org.farng.mp3.AbstractMP3Tag;
import org.farng.mp3.InvalidTagException;
import org.farng.mp3.MP3File;
import org.farng.mp3.TagConstant;
import org.farng.mp3.TagException;
import org.farng.mp3.TagNotFoundException;
import org.farng.mp3.TagOptionSingleton;

import java.io.IOException;
import java.io.RandomAccessFile;

import java.util.HashMap;
import java.util.Iterator;


/**
 * Title:       ID3v2_2 Description: Class that represents an ID3v2.20 tag
 * Copyright:   Copyright (c) 2002 Company:
 *
 * @author Eric Farng
 * @version $Revision: 1.8 $
 */
public class ID3v2_2 extends AbstractID3v2 {
    /** DOCUMENT ME! */
    protected boolean compression = false;

    /** DOCUMENT ME! */
    protected boolean unsynchronization = false;

    /**
     * Creates a new ID3v2_2 object.
     */
    public ID3v2_2() {
        frameMap     = new HashMap();
        majorVersion = 2;
        revision     = 2;
    }

    public ID3v2_2(ID3v2_2 copyObject) {
        super(copyObject);
        this.compression       = copyObject.compression;
        this.unsynchronization = copyObject.unsynchronization;
    }

    public ID3v2_2(AbstractMP3Tag mp3tag) {
        if (mp3tag != null) {
            ID3v2_4 convertedTag;

            if ((mp3tag instanceof ID3v2_3 == false) && (mp3tag instanceof ID3v2_2 == true)) {
                throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
            } else if (mp3tag instanceof ID3v2_4) {
                convertedTag = (ID3v2_4) mp3tag;
            } else {
                convertedTag = new ID3v2_4(mp3tag);
            }

            this.compression       = convertedTag.compression;
            this.unsynchronization = convertedTag.unsynchronization;

            AbstractID3v2      id3tag = (AbstractID3v2) convertedTag;
            Iterator           iterator = id3tag.frameMap.values().iterator();
            AbstractID3v2Frame frame;
            ID3v2_2Frame       newFrame;

            while (iterator.hasNext()) {
                frame    = (AbstractID3v2Frame) iterator.next();
                newFrame = new ID3v2_2Frame(frame);
                frameMap.put(newFrame.getIdentifier(), newFrame);
            }
        }
    }

    /**
     * Creates a new ID3v2_2 object.
     *
     * @param file DOCUMENT ME!
     *
     * @throws TagException DOCUMENT ME!
     * @throws IOException DOCUMENT ME!
     */
    public ID3v2_2(RandomAccessFile file)
            throws TagException, IOException {
        this.read(file);
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public String getIdentifier() {
        return "ID3v2_2.20";
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public int getSize() {
        int          size = 3 + 2 + 1 + 4;
        Iterator     iterator = frameMap.values().iterator();
        ID3v2_2Frame frame;

        while (iterator.hasNext()) {
            frame = (ID3v2_2Frame) iterator.next();
            size += frame.getSize();
        }

        return size;
    }

    /**
     * DOCUMENT ME!
     *
     * @param tag DOCUMENT ME!
     */
    public void append(AbstractMP3Tag tag) {
        if (tag instanceof ID3v2_2) {
            this.unsynchronization = ((ID3v2_2) tag).unsynchronization;
            this.compression       = ((ID3v2_2) tag).compression;
        }

        super.append(tag);
    }

    /**
     * DOCUMENT ME!
     *
     * @param obj DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */

//    public void append(RandomAccessFile file)
//                throws IOException, TagException {
//        ID3v2_2 oldTag;
//
//        try {
//            oldTag = new ID3v2_2(file);
//            oldTag.append(this);
//            oldTag.write(file);
//        } catch (TagNotFoundException ex) {
//            oldTag = null;
//        }
//    }

    /**
     * DOCUMENT ME!
     *
     * @param obj DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public boolean equals(Object obj) {
        if ((obj instanceof ID3v2_2) == false) {
            return false;
        }

        ID3v2_2 object = (ID3v2_2) obj;

        if (this.compression != object.compression) {
            return false;
        }

        if (this.unsynchronization != object.unsynchronization) {
            return false;
        }

        return super.equals(obj);
    }

    /**
     * DOCUMENT ME!
     *
     * @param tag DOCUMENT ME!
     */
    public void overwrite(AbstractMP3Tag tag) {
        if (tag instanceof ID3v2_2) {
            this.unsynchronization = ((ID3v2_2) tag).unsynchronization;
            this.compression       = ((ID3v2_2) tag).compression;
        }

        super.overwrite(tag);
    }

    /**
     * DOCUMENT ME!
     *
     * @param file DOCUMENT ME!
     *
     * @throws TagException DOCUMENT ME!
     * @throws IOException DOCUMENT ME!
     * @throws TagNotFoundException DOCUMENT ME!
     */

//    public void overwrite(RandomAccessFile file)
//                   throws IOException, TagException {
//        ID3v2_2 oldTag;
//
//        try {
//            oldTag = new ID3v2_2(file);
//            oldTag.overwrite(this);
//            oldTag.write(file);
//        } catch (TagNotFoundException ex) {
//	    // no more super classes
//	    //super.overwrite(file);
//        }
//    }

    /**
     * DOCUMENT ME!
     *
     * @param file DOCUMENT ME!
     *
     * @throws TagException DOCUMENT ME!
     * @throws IOException DOCUMENT ME!
     * @throws TagNotFoundException DOCUMENT ME!
     */
    public void read(RandomAccessFile file)
              throws TagException, IOException {
        int          size;
        ID3v2_2Frame next;
        byte[]       buffer = new byte[4];

        if (seek(file) == false) {
            throw new TagNotFoundException("ID3v2.20 tag not found");
        }

        // read the major and minor @version number & flags byte
        file.read(buffer, 0, 3);

        if ((buffer[0] != 2) || (buffer[1] != 0)) {
            throw new TagNotFoundException(getIdentifier() + " tag not found");
        }

        majorVersion      = buffer[0];
        revision          = buffer[1];
        unsynchronization = (buffer[2] & TagConstant.MASK_V22_UNSYNCHRONIZATION) != 0;
        compression       = (buffer[2] & TagConstant.MASK_V22_COMPRESSION) != 0;

        // read the size
        file.read(buffer, 0, 4);
        size = byteArrayToSize(buffer);

        frameMap = new HashMap();

        long filePointer = file.getFilePointer();

        // read all frames
        this.fileReadSize      = size;
        ID3v2_2.paddingCounter = 0;

        while ((file.getFilePointer() - filePointer) <= size) {
            try {
                next = new ID3v2_2Frame(file);

                String id = next.getIdentifier();

                if (frameMap.containsKey(id)) {
                    this.duplicateFrameId += (id + "; ");
                    this.duplicateBytes += ((AbstractID3v2Frame) frameMap.get(id)).getSize();
                }

                frameMap.put(id, next);
            } catch (InvalidTagException ex) {
                if (ex.getMessage().equals("Found empty frame")) {
                    this.emptyFrameBytes += 10;
                } else {
                    this.invalidFrameBytes++;
                }

                if ((file.getFilePointer() - filePointer) <= size) {
                    //System.out.println(ex.getMessage());
                }
            }

            ;
        }

        this.padding = ID3v2_2.paddingCounter;

        /**
         * int newSize = this.getSize(); if ((this.padding + newSize - 10) !=
         * size) { System.out.println("WARNING: Tag sizes don't add up");
         * System.out.println("ID3v2.20 tag size : " + newSize);
         * System.out.println("ID3v2.20 padding  : " + this.padding);
         * System.out.println("ID3v2.20 total    : " + (this.padding +
         * newSize)); System.out.println("ID3v2.20 file size: " + size); }
         */
    }

    /**
     * DOCUMENT ME!
     *
     * @param file DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     *
     * @throws IOException DOCUMENT ME!
     */
    public boolean seek(RandomAccessFile file)
                 throws IOException {
        byte[] buffer = new byte[3];

        file.seek(0);

        // read the tag if it exists
        file.read(buffer, 0, 3);

        String tag = new String(buffer, 0, 3);

        if (tag.equals("ID3") == false) {
            return false;
        }

        // read the major and minor @version number
        file.read(buffer, 0, 2);

        // read back the @version bytes so we can read and save them later
        file.seek(file.getFilePointer() - 2);

        return ((buffer[0] == 2) && (buffer[1] == 0));
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public String toString() {
        Iterator     iterator = frameMap.values().iterator();
        ID3v2_2Frame frame;
        String       str = getIdentifier() + " - " + this.getSize() + " bytes\n";
        str += ("compression        = " + compression + "\n");
        str += ("unsynchronization  = " + unsynchronization + "\n");

        while (iterator.hasNext()) {
            frame = (ID3v2_2Frame) iterator.next();
            str += (frame.toString() + "\n");
        }

        return str + "\n";
    }

    /**
     * DOCUMENT ME!
     *
     * @param tag DOCUMENT ME!
     */
    public void write(AbstractMP3Tag tag) {
        if (tag instanceof ID3v2_2) {
            this.unsynchronization = ((ID3v2_2) tag).unsynchronization;
            this.compression       = ((ID3v2_2) tag).compression;
        }

        super.write(tag);
    }

    /**
     * DOCUMENT ME!
     *
     * @param file DOCUMENT ME!
     *
     * @throws IOException DOCUMENT ME!
     */
    public void write(RandomAccessFile file)
               throws IOException {
        String       str;
        ID3v2_2Frame frame;
        Iterator     iterator;
        byte[]       buffer = new byte[6];

        MP3File      mp3 = new MP3File();
        mp3.seekMP3Frame(file);

        long mp3start = file.getFilePointer();

        if(!TagOptionSingleton.getInstance().isId3v2PaddingCopyTag()){
            // write zero tag
            byte[] zerobuffer = new byte[(int)mp3start];
            file.seek(0);
            file.write(zerobuffer, 0, zerobuffer.length);
        }
        
        
        file.seek(0);

        // write the first 10 tag bytes
        str = "ID3";

        for (int i = 0; i < str.length(); i++) {
            buffer[i] = (byte) str.charAt(i);
        }

        buffer[3] = 2;
        buffer[4] = 0;

        if (unsynchronization) {
            buffer[5] |= TagConstant.MASK_V22_UNSYNCHRONIZATION;
        }

        if (compression) {
            buffer[5] |= TagConstant.MASK_V22_COMPRESSION;
        }

        file.write(buffer);

        //write size;
        file.write(sizeToByteArray((int) mp3start - 10));

        // write all frames
        iterator = frameMap.values().iterator();

        while (iterator.hasNext()) {
            frame = (ID3v2_2Frame) iterator.next();
            frame.write(file);
        }

        if (this.getSize() != file.getFilePointer()) {
            //System.out.println("ID3v2_2.20 size didn't match up while writing.");
            //System.out.println("this.getsize()     = " + this.getSize());
            //System.out.println("size (filePointer) = " + file.getFilePointer());
        }
    }
}